"""
LSP 统一重命名接口

提供跨语言的统一符号重命名功能，自动选择合适的 LSP 客户端。
"""

import re
from pathlib import Path
from typing import Dict, List, Optional, Set
from dataclasses import dataclass, field

from .lsp_client import LSPClient, LSPWorkspaceEdit
from .swift_lsp_client import SwiftLSPClient
from .objc_lsp_client import ObjCLSPClient
from .java_lsp_client import JavaLSPClient
from .kotlin_lsp_client import KotlinLSPClient
from core.utils_module.language_utils import (
    SUPPORTED_LANGUAGES,
    canonical_language,
    detect_language_for_file,
    get_extension_group,
    language_to_extensions,
)

LSP_LANGUAGE_ALIAS = {
    'objective-c': 'objc',
}


@dataclass
class RenameResult:
    """重命名结果"""
    success: bool
    total_symbols: int = 0
    renamed_symbols: int = 0
    failed_symbols: int = 0
    edits: Dict[str, List[tuple]] = field(default_factory=dict)  # file_path -> [(line, old, new)]
    errors: List[str] = field(default_factory=list)
    warnings: List[str] = field(default_factory=list)


class LSPRenamer:
    """
    LSP 统一重命名器

    支持多语言符号重命名:
    - Swift (SourceKit-LSP)
    - Objective-C (clangd)
    - Java (Eclipse JDT Language Server)
    - Kotlin (Kotlin Language Server)
    - C/C++ (clangd)

    功能:
    - 自动语言检测
    - 批量重命名
    - 回退机制 (LSP 失败时使用 AST)
    - 详细的错误报告
    """

    def __init__(self, workspace_path: str, fallback_renamer=None):
        """
        初始化重命名器

        Args:
            workspace_path: 工作区路径
            fallback_renamer: 回退重命名器 (LSP 失败时使用)
        """
        self.workspace_path = Path(workspace_path).resolve()
        self.fallback_renamer = fallback_renamer
        self.clients: Dict[str, LSPClient] = {}
        self.language_file_cache: Dict[str, List[Path]] = {}

    def rename_symbols(self, symbol_map: Dict[str, str]) -> RenameResult:
        """
        批量重命名符号

        Args:
            symbol_map: 符号映射 {old_name: new_name}

        Returns:
            RenameResult: 重命名结果

        Example:
            >>> renamer = LSPRenamer('/path/to/project')
            >>> symbol_map = {'MyClass': 'ObfuscatedClass', 'myMethod': 'a'}
            >>> result = renamer.rename_symbols(symbol_map)
            >>> print(f"重命名成功: {result.renamed_symbols}/{result.total_symbols}")
        """
        result = RenameResult(success=True, total_symbols=len(symbol_map))

        # 按文件类型分组符号
        symbols_by_lang = self._group_symbols_by_language(symbol_map)

        # 对每种语言进行重命名
        for lang, symbols in symbols_by_lang.items():
            lang_result = self._rename_for_language(lang, symbols)
            result.renamed_symbols += lang_result.renamed_symbols
            result.failed_symbols += lang_result.failed_symbols
            result.edits.update(lang_result.edits)
            result.errors.extend(lang_result.errors)
            result.warnings.extend(lang_result.warnings)

        # 判断整体成功/失败
        if result.failed_symbols > 0:
            result.success = False

        return result

    def rename_symbol_in_file(
        self,
        file_path: str,
        old_name: str,
        new_name: str,
        line: Optional[int] = None,
        character: Optional[int] = None
    ) -> RenameResult:
        """
        在文件中重命名单个符号

        Args:
            file_path: 文件路径
            old_name: 旧名称
            new_name: 新名称
            line: 符号所在行号 (0-based)，None 则自动查找
            character: 符号所在列号 (0-based)，None 则自动查找

        Returns:
            RenameResult: 重命名结果
        """
        file_path = Path(file_path).resolve()
        result = RenameResult(success=False, total_symbols=1)

        # 检测语言
        lang = self._detect_language(file_path)
        if not lang:
            result.errors.append(f"无法识别文件语言: {file_path}")
            result.failed_symbols = 1
            return result

        # 获取或创建 LSP 客户端
        client = self._get_client(lang)
        if not client:
            result.errors.append(f"无法创建 {lang} LSP 客户端")
            result.failed_symbols = 1
            return self._fallback_rename(file_path, old_name, new_name, result)

        try:
            # 打开文档
            if not client.open_document(str(file_path)):
                raise Exception("打开文档失败")

            # 如果没有提供位置，查找符号位置
            if line is None or character is None:
                pos = self._find_symbol_position(client, str(file_path), old_name)
                if not pos:
                    raise Exception(f"找不到符号 '{old_name}'")
                line, character = pos

            # 执行重命名
            workspace_edit = client.rename(str(file_path), line, character, new_name)
            if not workspace_edit or not workspace_edit.changes:
                raise Exception("重命名失败，无编辑内容")

            # 应用编辑
            self._apply_workspace_edit(workspace_edit, result)
            result.success = True
            result.renamed_symbols = 1

        except Exception as e:
            result.errors.append(f"{file_path}: {str(e)}")
            result.failed_symbols = 1
            return self._fallback_rename(file_path, old_name, new_name, result)

        finally:
            # 关闭文档
            try:
                client.close_document(str(file_path))
            except:
                pass

        return result

    def _group_symbols_by_language(self, symbol_map: Dict[str, str]) -> Dict[str, Dict[str, str]]:
        """
        按语言分组符号

        Args:
            symbol_map: 符号映射

        Returns:
            {language: {old_name: new_name}}
        """
        # 检测工作区中存在的语言
        detected_languages = self._detect_workspace_languages()

        if not detected_languages:
            return {
                'swift': dict(symbol_map),
                'objc': dict(symbol_map),
                'java': dict(symbol_map),
                'kotlin': dict(symbol_map),
            }

        grouped: Dict[str, Dict[str, str]] = {lang: {} for lang in detected_languages}

        for old_name, new_name in symbol_map.items():
            assigned = False
            for lang in detected_languages:
                candidates = self._find_candidate_files(lang, old_name, max_candidates=1)
                if candidates:
                    grouped.setdefault(lang, {})[old_name] = new_name
                    assigned = True
                    break

            if not assigned:
                for lang in detected_languages:
                    grouped.setdefault(lang, {})[old_name] = new_name

        return {lang: mapping for lang, mapping in grouped.items() if mapping}

    def _rename_for_language(self, language: str, symbol_map: Dict[str, str]) -> RenameResult:
        """
        对特定语言进行重命名

        Args:
            language: 语言类型
            symbol_map: 符号映射

        Returns:
            RenameResult
        """
        result = RenameResult(success=True, total_symbols=len(symbol_map))

        # 获取客户端
        client = self._get_client(language)
        if not client:
            result.errors.append(f"不支持的语言: {language}")
            result.failed_symbols = len(symbol_map)
            result.success = False
            return result

        # 逐个重命名符号
        for old_name, new_name in symbol_map.items():
            try:
                candidate_files = self._find_candidate_files(language, old_name)
                file_result = self._rename_in_workspace(
                    client,
                    old_name,
                    new_name,
                    candidate_files=candidate_files if candidate_files else None
                )

                if not file_result.success and candidate_files:
                    fallback_result = self._rename_in_workspace(client, old_name, new_name)
                    if fallback_result.success:
                        file_result = fallback_result
                    else:
                        for err in fallback_result.errors:
                            if err not in file_result.errors:
                                file_result.errors.append(err)
                        for warn in fallback_result.warnings:
                            if warn not in file_result.warnings:
                                file_result.warnings.append(warn)
                        file_result.failed_symbols = max(
                            file_result.failed_symbols, fallback_result.failed_symbols
                        )

                result.errors.extend(file_result.errors)
                result.warnings.extend(file_result.warnings)

                if file_result.success:
                    result.renamed_symbols += file_result.renamed_symbols
                    result.edits.update(file_result.edits)
                else:
                    result.failed_symbols += file_result.failed_symbols or 1

            except Exception as e:
                result.failed_symbols += 1
                result.errors.append(f"{old_name}: {str(e)}")

        return result

    def _rename_in_workspace(
        self,
        client: LSPClient,
        old_name: str,
        new_name: str,
        candidate_files: Optional[List[Path]] = None
    ) -> RenameResult:
        """
        在工作区中重命名符号

        实现策略:
        1. 在工作区中搜索可能包含该符号的文件
        2. 对每个文件使用 documentSymbol 查找符号定义
        3. 找到后使用 rename 进行重命名

        Args:
            client: LSP 客户端
            old_name: 旧名称
            new_name: 新名称

        Returns:
            RenameResult
        """
        result = RenameResult(success=False, total_symbols=1)

        # 获取语言对应的文件扩展名
        language_id = client._get_language_id()
        extensions = self._get_extensions_for_language(language_id)

        if not extensions:
            result.failed_symbols = 1
            result.errors.append(f"未知语言类型: {language_id}")
            return result

        # 搜索工作区中的相关文件
        if candidate_files is None:
            candidate_files = self._get_language_files(language_id, extensions)
        else:
            candidate_files = [Path(p) for p in candidate_files]

        # 去重保持顺序
        candidate_files = list(dict.fromkeys(candidate_files))

        if not candidate_files:
            result.failed_symbols = 1
            result.warnings.append(f"工作区中未找到 {extensions} 文件")
            return result

        # 在文件中查找符号
        found = False
        for file_path in candidate_files:
            try:
                # 打开文档
                if not client.open_document(str(file_path)):
                    continue

                # 查找符号位置
                position = client.find_symbol_by_name(str(file_path), old_name)
                if position:
                    # 找到符号，执行重命名
                    line, character = position
                    workspace_edit = client.rename(str(file_path), line, character, new_name)

                    if workspace_edit and workspace_edit.changes:
                        self._apply_workspace_edit(workspace_edit, result)
                        result.success = True
                        result.renamed_symbols = 1
                        found = True
                        client.close_document(str(file_path))
                        break

                # 关闭文档
                client.close_document(str(file_path))

            except Exception as e:
                result.errors.append(f"{file_path}: {str(e)}")

        if not found:
            result.failed_symbols = 1
            result.warnings.append(f"在工作区中未找到符号: {old_name}")

        return result

    def _get_language_files(self, language_id: str, extensions: List[str]) -> List[Path]:
        """
        根据语言缓存并返回对应的文件列表。
        """
        cached = self.language_file_cache.get(language_id)
        if cached is not None:
            return cached

        files: List[Path] = []
        for ext in extensions:
            files.extend(self.workspace_path.rglob(f"*{ext}"))

        self.language_file_cache[language_id] = files
        return files

    def _find_candidate_files(self, language: str, symbol: str, max_candidates: int = 20) -> List[Path]:
        """
        基于简单的文本搜索为符号寻找候选文件，减少 LSP 遍历范围。
        """
        extensions = self._get_extensions_for_language(language)
        if not extensions:
            return []

        files = self._get_language_files(language, extensions)
        if not files:
            return []

        pattern = re.compile(r'\b{}\b'.format(re.escape(symbol)))
        pattern_ignore_case = re.compile(r'\b{}\b'.format(re.escape(symbol)), re.IGNORECASE)
        candidates: List[Path] = []

        for file_path in files:
            try:
                with open(file_path, 'r', encoding='utf-8', errors='ignore') as fh:
                    content = fh.read()
                    if pattern.search(content) or pattern_ignore_case.search(content):
                        candidates.append(file_path)
                        if len(candidates) >= max_candidates:
                            break
            except (OSError, UnicodeDecodeError):
                continue

        return candidates

    def _get_extensions_for_language(self, language_id: str) -> List[str]:
        """
        获取语言对应的文件扩展名

        Args:
            language_id: 语言 ID

        Returns:
            扩展名列表
        """
        internal_language = canonical_language(LSP_LANGUAGE_ALIAS.get(language_id, language_id))

        if internal_language in SUPPORTED_LANGUAGES:
            return language_to_extensions(internal_language)

        if internal_language in {'c', 'cpp'}:
            return get_extension_group(internal_language)

        return []

    def _get_client(self, language: str) -> Optional[LSPClient]:
        """
        获取或创建 LSP 客户端

        Args:
            language: 语言类型

        Returns:
            LSPClient 或 None
        """
        # 如果已存在，直接返回
        if language in self.clients:
            return self.clients[language]

        # 创建新客户端
        client = None
        if language == 'swift':
            client = SwiftLSPClient(str(self.workspace_path))
        elif language in ['objc', 'c', 'cpp']:
            client = ObjCLSPClient(str(self.workspace_path))
        elif language == 'java':
            client = JavaLSPClient(str(self.workspace_path))
        elif language == 'kotlin':
            client = KotlinLSPClient(str(self.workspace_path))

        if client and client.start():
            self.clients[language] = client
            return client

        return None

    def _detect_language(self, file_path: Path) -> Optional[str]:
        """
        检测文件语言

        Args:
            file_path: 文件路径

        Returns:
            语言类型 ('swift', 'objc', 'java', 'kotlin', 'c', 'cpp') 或 None
        """
        language = detect_language_for_file(str(file_path))
        if language:
            return language

        ext = file_path.suffix.lower()
        if ext == '.java':
            return 'java'
        if ext in {'.kt', '.kts'}:
            return 'kotlin'
        if ext in get_extension_group('c'):
            return 'c'
        if ext in get_extension_group('cpp'):
            return 'cpp'
        return None

    def _detect_workspace_languages(self) -> List[str]:
        """
        检测工作区中存在的语言

        Returns:
            语言列表 ['swift', 'objc', 'java', 'kotlin']
        """
        languages = set()

        def has_extension(exts: List[str]) -> bool:
            for ext in exts:
                if next(self.workspace_path.rglob(f"*{ext}"), None):
                    return True
            return False

        for language in SUPPORTED_LANGUAGES:
            if has_extension(language_to_extensions(language)):
                languages.add(language)

        for group_name in ['c', 'cpp']:
            extensions = get_extension_group(group_name)
            if extensions and has_extension(list(extensions)):
                languages.add(group_name)

        return list(languages)

    def _find_symbol_position(
        self,
        client: LSPClient,
        file_path: str,
        symbol_name: str
    ) -> Optional[tuple]:
        """
        在文件中查找符号位置

        Args:
            client: LSP 客户端
            file_path: 文件路径
            symbol_name: 符号名称

        Returns:
            (line, character) 或 None
        """
        try:
            # 优先使用 LSP 的 documentSymbol 查找
            position = client.find_symbol_by_name(file_path, symbol_name)
            if position:
                return position

            # 回退：简单文本搜索
            content = Path(file_path).read_text(encoding='utf-8')
            lines = content.split('\n')

            # 简单搜索符号出现的位置
            for line_num, line in enumerate(lines):
                char_pos = line.find(symbol_name)
                if char_pos != -1:
                    # 验证是否是符号定义或引用
                    # 检查前后是否是单词边界
                    if char_pos > 0 and line[char_pos - 1].isalnum():
                        continue
                    if char_pos + len(symbol_name) < len(line) and line[char_pos + len(symbol_name)].isalnum():
                        continue
                    return (line_num, char_pos)

        except Exception as e:
            print(f"查找符号位置失败: {e}")

        return None

    def _apply_workspace_edit(self, workspace_edit: LSPWorkspaceEdit, result: RenameResult):
        """
        应用工作区编辑

        Args:
            workspace_edit: 工作区编辑
            result: 结果对象 (会被修改)
        """
        for uri, edits in workspace_edit.changes.items():
            # 从 URI 转换为文件路径
            file_path = self._uri_to_path(uri)

            try:
                # 读取原始文件内容
                with open(file_path, 'r', encoding='utf-8') as f:
                    content = f.read()
                    lines = content.splitlines(keepends=True)

                # 按行号倒序排序编辑，从后往前应用，避免偏移问题
                sorted_edits = sorted(edits, key=lambda e: (e.range.start.line, e.range.start.character), reverse=True)

                # 应用每个编辑
                for edit in sorted_edits:
                    start_line = edit.range.start.line
                    start_char = edit.range.start.character
                    end_line = edit.range.end.line
                    end_char = edit.range.end.character

                    # 记录编辑信息
                    if start_line < len(lines):
                        old_text = ""
                        if start_line == end_line:
                            # 单行编辑
                            line = lines[start_line] if start_line < len(lines) else ""
                            old_text = line[start_char:end_char] if len(line) > start_char else ""
                        else:
                            # 多行编辑
                            for i in range(start_line, min(end_line + 1, len(lines))):
                                if i == start_line:
                                    old_text += lines[i][start_char:] if len(lines[i]) > start_char else lines[i]
                                elif i == end_line:
                                    old_text += lines[i][:end_char] if len(lines[i]) > end_char else lines[i]
                                else:
                                    old_text += lines[i]

                        edit_info = (
                            start_line,
                            old_text.strip(),
                            edit.new_text
                        )
                        if file_path not in result.edits:
                            result.edits[file_path] = []
                        result.edits[file_path].append(edit_info)

                    # 应用编辑到内容
                    if start_line == end_line:
                        # 单行替换
                        if start_line < len(lines):
                            line = lines[start_line]
                            # 确保不越界
                            if len(line) >= end_char:
                                new_line = line[:start_char] + edit.new_text + line[end_char:]
                            else:
                                new_line = line[:start_char] + edit.new_text
                            lines[start_line] = new_line
                    else:
                        # 多行替换
                        # 构建新的行内容
                        if start_line < len(lines):
                            first_line = lines[start_line][:start_char] + edit.new_text
                            if end_line < len(lines):
                                last_line = lines[end_line][end_char:] if len(lines[end_line]) > end_char else ""
                                first_line += last_line

                            # 删除中间的行
                            del lines[start_line:end_line + 1]
                            # 插入新行
                            lines.insert(start_line, first_line)

                # 写回文件
                new_content = ''.join(lines)
                with open(file_path, 'w', encoding='utf-8') as f:
                    f.write(new_content)

            except Exception as e:
                result.errors.append(f"应用编辑失败 {file_path}: {str(e)}")
                result.success = False

    def _uri_to_path(self, uri: str) -> str:
        """
        将 URI 转换为文件路径

        Args:
            uri: file:// URI

        Returns:
            文件路径
        """
        if uri.startswith('file://'):
            return uri[7:]  # 移除 'file://' 前缀
        return uri

    def _fallback_rename(
        self,
        file_path: Path,
        old_name: str,
        new_name: str,
        result: RenameResult
    ) -> RenameResult:
        """
        回退到 AST/正则重命名

        Args:
            file_path: 文件路径
            old_name: 旧名称
            new_name: 新名称
            result: 当前结果 (会被修改)

        Returns:
            更新后的 RenameResult
        """
        if not self.fallback_renamer:
            result.warnings.append(f"LSP 失败且无回退重命名器: {file_path}")
            return result

        try:
            # 使用回退重命名器
            fallback_result = self.fallback_renamer.rename(file_path, old_name, new_name)
            if fallback_result:
                result.success = True
                result.renamed_symbols = 1
                result.failed_symbols = 0
                result.warnings.append(f"使用回退重命名: {file_path}")
        except Exception as e:
            result.errors.append(f"回退重命名失败: {str(e)}")

        return result

    def cleanup(self):
        """清理所有 LSP 客户端"""
        for client in self.clients.values():
            try:
                client.stop()
            except:
                pass
        self.clients.clear()

    def __enter__(self):
        """上下文管理器入口"""
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """上下文管理器退出"""
        self.cleanup()
